package org.windowkit.toolkit.widget.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.swing.AbstractSpinnerModel;
import javax.swing.text.DateFormatter;

/**
 *
 * <table width="100%" border="1">
 * <tr>
 * <th colspan="3" align="center">HISTORY</th>
 * </tr>
 * <tr>
 * <th>DATE</th><th>DEVELOPER</th><th>DESCRIPTION</th>
 * </tr>
 * <tr>
 * <td>02/22/2007</td>
 * <td>Phong Kien</td>
 * <td>Add a fixed to allow getNextValue() to return next date even if
 * <b>end</b> is null which signified no upper bound.</td>
 * </tr>
 * </table>
 * @author Eric Lundin
 *
 */
public class SpinnerDateModel extends AbstractSpinnerModel {
	private String value;

	SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

	DateFormatter dateFormatter = new DateFormatter(dateFormat);

	/** Tracks current field to increment or decrement */
	int currentCalendarField;

	/** Upper bound for date */
	Date start;

	/** Lower bound for date */
	Date end;

	/** Calendar to assist in date math. */
	Calendar calendar;

	/**
	 * @param value
	 *            The initial value.
	 * @param start
	 *            The lower bound for the date.
	 * @param end
	 *            The upper bound for the date.
	 * @increment The calendar field to increment.
	 */
	public SpinnerDateModel(final Date initialValue, final Comparable start,
			final Comparable end, final int increment) throws ParseException {
		super();
		value = new String();

		this.start = (Date) start;
		this.end = (Date) end;

		this.value = dateFormatter.valueToString(initialValue);
		setCalendarField(increment);
		dateFormatter.setCommitsOnValidEdit(true);
	}

	/**
	 * @param initialValue
	 * @param lowerBound
	 * @param upperBound
	 * @param increment
	 */
	public SpinnerDateModel(final String initialValue, final String start, final String end,
			final int increment) throws ParseException {
		super();
		value = new String();
		this.start = start == null ? null : (Date) dateFormatter
				.stringToValue(start);
		this.end = end == null ? null : (Date) dateFormatter.stringToValue(end);
		this.value = dateFormatter.valueToString(dateFormatter
				.stringToValue(initialValue));
		setCalendarField(increment);
		dateFormatter.setCommitsOnValidEdit(true);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.SpinnerModel#getValue()
	 */
	public Object getValue() {
		return value;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.SpinnerModel#setValue(java.lang.Object)
	 */
	public void setValue(final Object newValue) {
		if (newValue == null) {
			return;
		}
		try {
			value = dateFormatter.valueToString(dateFormatter
					.stringToValue((String) newValue));
		} catch (ParseException e) {
			value = (String) newValue;
		}
	}

	/**
	 * Determine if current date is valid.
	 *
	 * @return True if date is valid. False otherwise.
	 */
	public boolean isDateValid() {
		boolean retVal = true;
		if (value == null) {
			retVal = false;
		}
		else {
			try {
				dateFormatter.stringToValue(value);
			} catch (ParseException e) {
				retVal = false;
			}
		}
		return retVal;
	}

	/**
	 * Sets the calendar field to be incremented or decremented.
	 *
	 * @param calendarField
	 *            The calendar field to increment or decrement. Must be one of
	 *            <UL>
	 *            <LI>Calendar.DAY_OF_MONTH</LI>
	 *            <LI>Calendar.MONTH</LI>
	 *            <LI>Calendar.YEAR</LI>
	 *            </UL>
	 * @throws IllegalArgumentException
	 *             If an invalid field is passed in.
	 */
	public void setCalendarField(final int calendarField) {
		switch (calendarField) {
		case Calendar.DAY_OF_MONTH:
		case Calendar.MONTH:
		case Calendar.YEAR:
			currentCalendarField = calendarField;
			break;
		default:
			throw new IllegalArgumentException("Invalid calendar field");
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.SpinnerModel#getNextValue()
	 */
	public Object getNextValue() {

		Date date;
		Date newDate;
		String nextValue = null;

		if (!isDateValid()) {
			if (value.equals("MM/DD/YYYY")) {
				try {
					value = dateFormatter.valueToString(new Date());
					nextValue = value;
				} catch (ParseException e) {
				}
			}
			else {
				nextValue = value;
			}
		} else {
			try {
				date = (Date) dateFormatter.stringToValue(value);
				calendar = Calendar.getInstance();
				calendar.setTime(date);
				calendar.add(currentCalendarField, 1);
				newDate = calendar.getTime();

				//allow date to increment if there is no upper bound.
				if (end == null) {
					nextValue = dateFormatter.valueToString(newDate);
				} else {
					if (newDate.before(end)) {
						nextValue = dateFormatter.valueToString(newDate);
					}
				}
			} catch (ParseException e) {
				nextValue = null;
			}
		}
		setValue(nextValue);
		fireStateChanged();
		return nextValue;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.swing.SpinnerModel#getPreviousValue()
	 */
	public Object getPreviousValue() {
		Date date;
		Date newDate;
		String nextValue = null;
		if (!isDateValid()) {
			if (value.equals("MM/DD/YYYY")) {
				try {
					value = dateFormatter.valueToString(new Date());
					nextValue = value;
				} catch (ParseException e) {
				}
			}
			else {
				nextValue = value;
			}
		} else {
			try {
				date = (Date) dateFormatter.stringToValue(value);
				calendar = Calendar.getInstance();
				calendar.setTime(date);
				calendar.add(currentCalendarField, -1);
				newDate = calendar.getTime();

				//allow date to decrement if there is no lower bound.
				if (start == null){
					nextValue = dateFormatter.valueToString(newDate);
				} else {
					if (newDate.after(start)) {
						nextValue = dateFormatter.valueToString(newDate);
					}
				}
			} catch (ParseException e) {
				nextValue = null;
			}
		}
		setValue(nextValue);
		fireStateChanged();
		return nextValue;
	}
}
